/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package network;

/**
 *
 * @author Raise
 */
import network.Layer;
import java.util.Random;

/**
 * 全连接层实现 实现标准的全连接神经网络层，包含前向传播和反向传播 使用ReLU激活函数和Xavier权重初始化
 */
public class FullyConnectedLayer extends Layer {

    // 权重矩阵：[输出维度 × 输入维度]，每个输出神经元对应一组输入权重
    private double[][] weights;
    // 偏置向量：[输出维度]，每个输出神经元对应一个偏置
    private double[] biases;
    // 最后一次前向传播的输入，用于反向传播时的梯度计算
    private double[] lastInput;
    private double[] lastSums;  // 保存前向传播的加权和(z)
    private boolean useRelu;

    public FullyConnectedLayer(int inputSize, int outputSize,boolean useRelu, Random rand) {
        this.inputSize = inputSize;   // 设置输入维度
        this.outputSize = outputSize; // 设置输出维度
        this.lastSums = new double[outputSize];
        this.useRelu = useRelu;  // 控制是否使用ReLU
        initializeWeights(rand);          // 初始化权重和偏置
    }

    /**
     * 初始化权重和偏置 使用Xavier初始化方法，适合与ReLU激活函数配合使用
     */
    private void initializeWeights(Random rand) {
        //Random rand = new Random();
        weights = new double[outputSize][inputSize];
        biases = new double[outputSize];

        // Xavier/Glorot初始化：使用高斯分布，标准差为 sqrt(2/inputSize)
        // 这种初始化方法有助于防止梯度消失/爆炸，特别适合ReLU激活函数
        double scale = Math.sqrt(2.0 / inputSize);

        // 初始化所有权重
        for (int i = 0; i < outputSize; i++) {
            for (int j = 0; j < inputSize; j++) {
                // 从高斯分布中采样并缩放，生成一个服从均值 = 0，标准差 = scale正态分布的随机数
                weights[i][j] = rand.nextGaussian() * scale;
            }
            // 初始化偏置为小的正值，避免ReLU的"死亡神经元"问题
            biases[i] = 0.1;
        }
    }

    /**
     * 前向传播 计算：output = ReLU(weights × input + biases)
     *
     * @param input 输入向量，长度为inputSize
     * @return 输出向量，长度为outputSize
     */
    @Override
    public double[] forward(double[] input) {
        // 保存输入，用于反向传播时的梯度计算
        this.lastInput = input.clone();
        double[] output = new double[outputSize];

        for (int i = 0; i < outputSize; i++) {
            double sum = biases[i];
            for (int j = 0; j < inputSize; j++) {
                sum += weights[i][j] * input[j];
            }
            lastSums[i] = sum;  // 保存加权和，用于反向传播
            // 根据useRelu决定是否激活
            if (useRelu) {
                output[i] = Math.max(0, sum); // ReLU激活
            } else {
                output[i] = sum; // 无激活，直接输出
            }
        }

        return output;
    }

    public double[] backward(double[] upstreamGradient, double learningRate) {
        // 1. 首先计算当前层的完整梯度（考虑ReLU激活函数）
//        输出层是特殊的：它的 upstreamGradient 直接来自损失函数
//        隐藏层是标准的：它们的 upstreamGradient 来自后一层的反向传播结果
        double[] currentGradient = new double[outputSize];
        for (int i = 0; i < outputSize; i++) {
            // δ_i = 上层传来的梯度 × ReLU导数(lastSums[i])
            currentGradient[i] = upstreamGradient[i] * reluDerivative(lastSums[i]);
        }

        // 2. 先计算传递给前一层的梯度（使用原始权重）
        double[] prevGradient = new double[inputSize];
        for (int j = 0; j < inputSize; j++) {
            for (int i = 0; i < outputSize; i++) {
                prevGradient[j] += currentGradient[i] * weights[i][j]; // 使用原始权重
            }
        }

        // 3. 再更新权重和偏置
        for (int i = 0; i < outputSize; i++) {
            for (int j = 0; j < inputSize; j++) {
                weights[i][j] -= learningRate * currentGradient[i] * lastInput[j];
            }
            biases[i] -= learningRate * currentGradient[i];
        }

        return prevGradient;
    }

    /**
     * ReLU激活函数的导数
     */
    private double reluDerivative(double x) {
        return x > 0 ? 1.0 : 0.0;
    }


    // 添加setter方法用于测试
    public void setWeights(double[][] newWeights) {
        this.weights = newWeights;
    }

    public void setBiases(double[] newBiases) {
        this.biases = newBiases;
    }

    // 添加getter方法
    public double[][] getWeights() {
        return weights;
    }

    public double[] getBiases() {
        return biases;
    }
}

//注意还没有写损失函数？！！！
